/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2013, 2014 Damien P. George
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdio.h>

#include "py/runtime.h"
#include "shared/timeutils/timeutils.h"
#include "machine.h"

// init rtc module
static void _rtc_init(machine_rtc_obj_t *self)
{
  	self->hpmu.Instance = PMU;
	self->hpmu.ClkSource = PMU_CLKSOURCE_32RC;
	HAL_PMU_Init(&self->hpmu);  
}

// start rtc or start rtc alarm
static mp_obj_t _rtc_start(machine_rtc_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, bool is_alarm)
{
    static const mp_arg_t allowed_args[] = {
        { MP_QSTR_year,     MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_YEAR} },
        { MP_QSTR_month,    MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_MONTH} },
        { MP_QSTR_date,     MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_DAY} },
        { MP_QSTR_weekday,  MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_WEEKDAY} },
        { MP_QSTR_hour,     MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_HOUR} },
        { MP_QSTR_minute,   MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_MINUTE} },
        { MP_QSTR_second,   MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_SECOND} },
        { MP_QSTR_timezone, MP_ARG_REQUIRED  | MP_ARG_INT, {.u_int = MICROPY_HW_RTC_TIMEZONE} }, 
        { MP_QSTR_callback, MP_ARG_KW_ONLY   | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
    };
    mp_obj_t *items;
    mp_obj_get_array_fixed_n(pos_args[0], 8, &items);
        
    // parse tupe args
    struct {
        mp_arg_val_t year, month, date, weekday, hour, minute, second, timezone, callback;
    } args;
    mp_arg_parse_all(8, items, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args);

    //printf("%d-%d-%d %d %d:%d:%d utc-%d\n", 
    //args.year.u_int, args.month.u_int, args.date.u_int, args.weekday.u_int, args.hour.u_int, args.minute.u_int, args.second.u_int, 
    //args.timezone.u_int);
    // fill structure
    self->time.Year = args.year.u_int - 2000;
	self->time.Month = args.month.u_int;
	self->time.Date = args.date.u_int;
	self->time.Hours = args.hour.u_int;
	self->time.Minutes = args.minute.u_int;
	self->time.Seconds = args.second.u_int;
    self->timezone = args.timezone.u_int;   // timezone
    
    // init clock source
    _rtc_init(self); 
    
    if(is_alarm){
        self->callback = (args.callback.u_obj == MP_OBJ_NULL) ? (mp_const_none) : (args.callback.u_obj);  // alarm callback
        HAL_PMU_RTC_Alarm_Enable(&self->hpmu, &self->time);
    }else{
        HAL_PMU_RTC_Start(&self->hpmu, &self->time);  // start clock
    }
    return mp_const_none;
}

STATIC void machine_rtc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
    machine_rtc_obj_t* self = MP_OBJ_TO_PTR(self_in);
    
    HAL_PMU_RTC_GetTime(&self->hpmu, &self->time);
    mp_int_t weekday = timeutils_calc_weekday(2000 + self->time.Year, self->time.Month, self->time.Date) + 1;
    mp_printf(print, "RTC(%d, %d, %d, %d, %d, %d, %d, %d)", self->time.Year+2000, self->time.Month, 
              self->time.Date, weekday, self->time.Hours, self->time.Minutes, self->time.Seconds, self->timezone);
}

// rtc.init((year, month, date, weekday, hour, minute, second, timezone))
STATIC mp_obj_t machine_rtc_init_helper(machine_rtc_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
    return _rtc_start(self, n_args, pos_args, kw_args, false);
}

/// \moduleref machine
/// \class RTC - real time clock
///
/// The RTC is an independent clock that keeps track of the date
/// and time.
///
/// Example usage:
///
///     rtc = machine.RTC()
///     rtc.init((2022, 01, 01, 01, 12, 56, 30, 0))
///     print(rtc.datetime())

/// \classmethod \constructor()
/// Create an RTC object.
STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
    
    machine_rtc_obj_t* self;
        
    if(MP_STATE_PORT(rtc_obj) == NULL){
        self = m_new0(machine_rtc_obj_t, 1);
        self->base.type = &machine_rtc_type;
        MP_STATE_PORT(rtc_obj) = self;
    }else{
        self = MP_STATE_PORT(rtc_obj);
    }
    
    if (n_args >= 1 || n_kw > 0) {
        // start the peripheral
        mp_map_t kw_args;
        mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
        //printf("alloc:%d, used:%d, is_fixed:%d, is_ordered:%d, table:%p, args:%p\n", 
       //kw_args.alloc, kw_args.used, kw_args.is_fixed, kw_args.is_ordered, kw_args.table, args);
        machine_rtc_init_helper(self, n_args, args, &kw_args);
    }
    // return constant object
    return MP_OBJ_FROM_PTR(self);
}

// init method
STATIC mp_obj_t machine_rtc_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
    //printf("alloc:%d, used:%d, is_fixed:%d, is_ordered:%d, table:%p, args:%p\n", 
    //kw_args->alloc, kw_args->used, kw_args->is_fixed, kw_args->is_ordered, kw_args->table, args);
    return machine_rtc_init_helper(MP_OBJ_TO_PTR(args[0]), n_args - 1, args + 1, kw_args);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_rtc_init_obj, 1, machine_rtc_init);



/// \method datetime([datetimetuple])
/// Get or set the date and time of the RTC.
///
/// With no arguments, this method returns an 8-tuple with the current
/// date and time.  With 1 argument (being an 8-tuple) it sets the date
/// and time.
///
/// The 8-tuple has the following format:
///
///     (year, month, day, weekday, hours, minutes, seconds, timezone)
///
/// `weekday` is 1-7 for Monday through Sunday.
///
/// `subseconds` counts down from 255 to 0

// storage params pass from user

//void _save_time_args(machine_rtc_obj_t* self,  mp_obj_t *items)
//{
 //   self->time.Year = mp_obj_get_int(items[0]) - 2000;
 //   self->time.Month = mp_obj_get_int(items[1]);
 //   self->time.Date = mp_obj_get_int(items[2]);
 //   //self->time.WeekDay = mp_obj_get_int(items[3]) - 1;
 //   self->time.Hours = mp_obj_get_int(items[4]);
 //   self->time.Minutes = mp_obj_get_int(items[5]);
 //   self->time.Seconds = mp_obj_get_int(items[6]);
 //   self->timezone = mp_obj_get_int(items[7]);
//}

mp_obj_t machine_rtc_datetime(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
    
    machine_rtc_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    
    if (n_args == 1) {
        HAL_PMU_RTC_GetTime(&self->hpmu, &self->time);
        mp_int_t weekday = timeutils_calc_weekday(2000 + self->time.Year, self->time.Month, self->time.Date) + 1;
        
        mp_obj_t tuple[8] = {
            mp_obj_new_int(2000 + self->time.Year),
            mp_obj_new_int(self->time.Month),
            mp_obj_new_int(self->time.Date),
            mp_obj_new_int(weekday),
            mp_obj_new_int(self->time.Hours),
            mp_obj_new_int(self->time.Minutes),
            mp_obj_new_int(self->time.Seconds),
            mp_obj_new_int(self->timezone),
        };
        return mp_obj_new_tuple(8, tuple);
    } else {
        // set date and time
        //mp_obj_t *items;
        //mp_obj_get_array_fixed_n(args[1], 8, &items);
        //_save_time_args(self, items);
        // restart rtc module
        //HAL_PMU_RTC_Start(&self->hpmu, &self->time);
        return _rtc_start(self, n_args-1, args+1, kw_args, false);
    }
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_rtc_datetime_obj, 1, machine_rtc_datetime);


/// \method alarm([datetimetuple], callback)
/// Get or set the date and time of the RTC.
///
///
/// The 8-tuple has the following format:
///
///     (year, month, day, weekday, hours, minutes, seconds, timezone)
///
/// `weekday` is 1-7 for Monday through Sunday.
///
/// `subseconds` counts down from 255 to 0
mp_obj_t machine_rtc_alarm(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
    
    machine_rtc_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    
    // save datetime tuple
//    mp_obj_t *items;
//    mp_obj_get_array_fixed_n(args[1], 8, &items);
//    _save_time_args(self, args[1]);
    // save callback
//    self->callback = mp_const_none;
//    if(n_args > 2 && mp_obj_is_fun(args[2])){
//        self->callback = args[2];
//        HAL_PMU_RTC_Alarm_Enable(&self->hpmu, &self->time);
//    }
//    return mp_const_none;
    return _rtc_start(self, n_args-1,args+1, kw_args, true);
}

MP_DEFINE_CONST_FUN_OBJ_KW(machine_rtc_alarm_obj, 2, machine_rtc_alarm);

mp_obj_t machine_rtc_alarm_cancel(mp_obj_t self_in) {
    machine_rtc_obj_t *self = MP_OBJ_TO_PTR(self_in);
    HAL_PMU_RTC_Alarm_Disable(&self->hpmu);
    return mp_const_none;
}

MP_DEFINE_CONST_FUN_OBJ_1(machine_rtc_alarm_cancel_obj, machine_rtc_alarm_cancel);


STATIC const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_rtc_init_obj) },
    { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) },
    { MP_ROM_QSTR(MP_QSTR_alarm), MP_ROM_PTR(&machine_rtc_alarm_obj) },
    { MP_ROM_QSTR(MP_QSTR_alarm_cancel), MP_ROM_PTR(&machine_rtc_alarm_cancel_obj) },
};
STATIC MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table);

const mp_obj_type_t machine_rtc_type = {
    { &mp_type_type },
    .name = MP_QSTR_RTC,
    .print = machine_rtc_print,
    .make_new = machine_rtc_make_new,
    .locals_dict = (mp_obj_dict_t *)&machine_rtc_locals_dict,
};
